#pragma once

#include "fmt/color.h"
#include "fmt/core.h"
#include "fmt/format.h"

#include <string>

namespace ibox::base {
constexpr size_t get_strlen(const char *str, size_t len = 0) {
    return str[len] == 0 ? get_strlen(str, len + 1) + 1 : 0;
}

// Get the file name based on the path at compile time
constexpr const char *get_shortname(const char *filename,
                                    size_t len = std::string::npos) {
    return len == std::string::npos
               ? (len = get_strlen(filename), get_shortname(filename, len))
           : (len > 0
              && (filename[len - 1] == '/' || filename[len - 1] == '\\'))
               ? filename + len
           : (len > 0) ? get_shortname(filename, len - 1)
                       : filename;
}

// 日志级别
enum class LogLevel { Debug, Info, Warning, Error, Fatal };

class Config {};

class GlobalConfig : public Config {
public:
    static auto Get() -> GlobalConfig & {
        static GlobalConfig instance;
        return instance;
    }

public:
    const char *log_filepath = nullptr;
    LogLevel log_level = LogLevel::Debug;
    bool log_console = true;
};

class source_location {
public:
    source_location()
        : m_filename(nullptr), m_func_name(nullptr), m_line_number(0) {}
    static constexpr source_location
    current(const char *filename = __builtin_FILE(),
            const char *func_name = __builtin_FUNCTION(),
            int line_number = __builtin_LINE()) noexcept {
        return source_location(filename, func_name, line_number);
    }

    constexpr const char *file_name() const noexcept { return m_filename; }

    constexpr const char *function_name() const noexcept { return m_func_name; }

    constexpr int line() const noexcept { return m_line_number; }

private:
    constexpr source_location(const char *filename,
                              const char *func_name,
                              const int line_number)
        : m_filename{filename}, m_func_name{func_name},
          m_line_number{line_number} {}

private:
    const char *m_filename;
    const char *m_func_name;
    const int m_line_number;
};

class context {
public:
    LogLevel level;
    unsigned int tid;
    int line{};
    const char *short_filename{};
    const char *long_filename{};
    const char *func_name{};
    std::string_view text;
};

template <typename... Args>
using format_string_t = fmt::format_string<Args...>;
using buffer_t = fmt::memory_buffer;

// Log 接口类
class Log {
public:
    static Log &instance() {
        thread_local Log t_log;
        return t_log;
    }

    inline static Log &Get(LogLevel level) {
        Log &ret = instance();
        ret.m_level = level;
        return ret;
    }

    static Log &GetInstance();

    static bool should_log(LogLevel level) {
        return GlobalConfig::Get().log_level <= level;
    }

    template <typename... Args>
    inline static void debug(format_string_t<Args...> format, Args &&...args) {
        if (should_log(LogLevel::Debug)) {
            Get(LogLevel::Debug).printf(format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void debug(source_location const &loc,
                             format_string_t<Args...> format,
                             Args &&...args) {
        if (should_log(LogLevel::Debug)) {
            Get(LogLevel::Debug)
                .printf(loc, format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void info(format_string_t<Args...> format, Args &&...args) {
        if (should_log(LogLevel::Info)) {
            Get(LogLevel::Info).printf(format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void info(source_location const &loc,
                            format_string_t<Args...> format,
                            Args &&...args) {
        if (should_log(LogLevel::Info)) {
            Get(LogLevel::Info)
                .printf(loc, format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void warning(format_string_t<Args...> format,
                               Args &&...args) {
        if (should_log(LogLevel::Warning)) {
            Get(LogLevel::Warning).printf(format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void warning(source_location const &loc,
                               format_string_t<Args...> format,
                               Args &&...args) {
        if (should_log(LogLevel::Warning)) {
            Get(LogLevel::Warning)
                .printf(loc, format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void error(format_string_t<Args...> format, Args &&...args) {
        if (should_log(LogLevel::Error)) {
            Get(LogLevel::Error).printf(format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    inline static void error(source_location const &loc,
                             format_string_t<Args...> format,
                             Args &&...args) {
        if (should_log(LogLevel::Error)) {
            Get(LogLevel::Error)
                .printf(loc, format, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    void printf(format_string_t<Args...> format, Args &&...args) const {
        context ctx;
        buffer_t buffer;
        fmt::format_to(
            std::back_inserter(buffer), format, std::forward<Args>(args)...);
        ctx.text = std::string_view{buffer.data(), buffer.size()};
        log_it(ctx);
    }

    template <typename... Args>
    void printf(source_location const &location,
                format_string_t<Args...> format,
                Args &&...args) const {
        context ctx;
        ctx.line = location.line();
        ctx.func_name = location.function_name();
        ctx.long_filename = location.file_name();
        ctx.short_filename = get_shortname(ctx.long_filename);
        buffer_t buffer;
        fmt::format_to(
            std::back_inserter(buffer), format, std::forward<Args>(args)...);
        ctx.text = std::string_view{buffer.data(), buffer.size()};
        log_it(ctx);
    }

    void log_it(context &ctx) const {
        ctx.level = m_level;
        ctx.tid = syscall(SYS_gettid);
        Get(ctx.level).do_log(ctx);
    }

    void do_log(const context &ctx) {
        // 当前只考虑console输出, 输出到文件后面有需要再搞
        log_console(ctx);
    }

    // 提供两种format方式
    void log_formatter(context const &ctx, buffer_t &buffer);
    void colorful_formatter(context const &ctx, buffer_t &buffer);

    void log_console(const context &ctx) {
        auto buffer = buffer_t{};
        // log_formatter(ctx, buffer);
        colorful_formatter(ctx, buffer);

        {
            std::lock_guard<std::mutex> lock(m_mutex); // Lock the I/O device
            ::fwrite_unlocked(buffer.data(), 1, buffer.size(), stdout);
        }
        std::fflush(stdout);
    }

private:
    LogLevel m_level{};
    // locked console
    std::mutex m_mutex;
};
} // namespace ibox::base

// log with position micro
#define LOG_DEBUG(fmt, ...)                                                    \
    ibox::base::Log::debug(                                                    \
        ibox::base::source_location::current(), fmt, ##__VA_ARGS__)

#define LOG_INFO(fmt, ...)                                                     \
    ibox::base::Log::info(                                                     \
        ibox::base::source_location::current(), fmt, ##__VA_ARGS__)

#define LOG_WARNING(fmt, ...)                                                  \
    ibox::base::Log::warning(                                                  \
        ibox::base::source_location::current(), fmt, ##__VA_ARGS__)

#define LOG_ERROR(fmt, ...)                                                    \
    ibox::base::Log::error(                                                    \
        ibox::base::source_location::current(), fmt, ##__VA_ARGS__)
